package uk.ac.rhul.cs.cl1.seeding;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;

import uk.ac.rhul.cs.cl1.growth.ClusterGrowthProcess;
import uk.ac.rhul.cs.graph.Graph;
import uk.ac.rhul.cs.graph.GraphAlgorithm;
import uk.ac.rhul.cs.utils.StringUtils;

/**
 * Abstract seed nodeset generator class.
 * 
 * A seed nodeset generator is an abstract algorithm that produces a set of candidate
 * seed nodesets from a graph. These candidate nodesets will be passed on to a
 * {@link ClusterGrowthProcess} during a {@link ClusterONEAlgorithm} to produce
 * the clusters.
 * 
 * Seed nodeset generators implement the {@link Iterable} interface, so the easiest
 * way to get the set of seed nodesets is to iterate over it in a for loop.
 *  
 * @author tamas
 */
public abstract class SeedGenerator extends GraphAlgorithm
implements Iterable<Seed>, Serializable {
	/**
	 * Constructs a seed generator that is not associated to any graph yet.
	 */
	public SeedGenerator() {
		this(null);
	}

	/**
	 * Constructs a seed generator that is associated to a given graph.
	 * @param graph   the graph the seed generator will operate on
	 */
	public SeedGenerator(Graph graph) {
		super(graph);
	}
	
	/**
	 * Returns the number of seeds that will be generated.
	 * If the number of seeds cannot be known in advance, -1 will be returned.
	 * @return   the expected number of seeds generated by the generator, or -1 if the
	 *           number of seeds is not known in advance.
	 */
	public abstract int size();
	
	/**
	 * Returns an iterator that will generate seeds
	 */
	public abstract SeedIterator iterator();
	
	/**
	 * Factory method that can construct seed generators from a simple string description.
	 * The following specifiers are recognised at the moment:
	 * 
	 * <ul>
	 * <li><tt>nodes</tt> - generates a singleton seed for each node of the graph</li>
	 * <li><tt>edges</tt> - generates a seed containing the two endpoints for each edge of the graph</li>
	 * <li><tt>cliques</tt> - generates a seed for every maximal clique in the graph</li>
	 * <li><tt>stdin</tt> - reads seeds from the standard input. Each line in the standard
	 *     input must contain node names spearated by spaces.
	 * <li><tt>file(<i>filename.txt</i>)</tt> - opens <tt>filename.txt</tt> and interprets
	 *     each line as a seed set. Lines in the file must contain node names separated by
	 *     spaces.</li> 
	 * <li><tt>single(<i>node1</i>,<i>node2</i>,[...],<i>nodeN</i>)</tt> - generates a
	 *     single seed with the given nodes.</li>
	 * </ul>
	 * 
	 * @param  specification   the specification string
	 * @param  graph           the graph used by the constructed seed generator
	 * @throws InstantiationException if the specification string is invalid or some error
	 *                                occurred (e.g., file not found for a file based seed
	 *                                generator)
	 */
	public static SeedGenerator fromString(String specification, Graph graph) throws InstantiationException {
		if (specification.equals("nodes"))
			return new EveryNodeSeedGenerator(graph);
		
		if (specification.equals("edges"))
			return new EveryEdgeSeedGenerator(graph);
		
		if (specification.equals("cliques"))
			return new MaximalCliqueSeedGenerator(graph);
		
		if (specification.equals("stdin"))
			return new StreamBasedSeedGenerator(graph, System.in);
		
		if (specification.startsWith("single(") && specification.endsWith(")")) {
			String seeds = StringUtils.substring(specification, 7, -1);
			StreamBasedSeedGenerator result = new StreamBasedSeedGenerator(
					graph, new BufferedReader(new StringReader(seeds))
			);
			result.setDelimiters(", \n\t\r");
			return result;
		}
		
		if (specification.startsWith("file(") && specification.endsWith(")")) {
			String filename = StringUtils.substring(specification, 5, -1);
			try {
				return new FileBasedSeedGenerator(graph, filename);
			} catch (FileNotFoundException ex) {
				throw new InstantiationException("file not found: "+filename);
			} catch (IOException ex) {
				throw new InstantiationException("IO error while reading file: "+filename);
			}
		}
		
		throw new InstantiationException("unknown seed generator type: "+specification);
	}
	
	/**
	 * Factory method that can construct seed generators from a simple string description.
	 * 
	 * The constructed seed generator will not be associated to any given graph yet.
	 * 
	 * @param  specification   the specification string
	 * @throws InstantiationException if the specification string is invalid
	 */
	public static SeedGenerator fromString(String specification) throws InstantiationException {
		return fromString(specification, null);
	}
}
